/*
 * Copyright (c) 2013 - 2015, Freescale Semiconductor, Inc.
 * Copyright 2016-2017 NXP
 * All rights reserved.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include "board.h"
#include "clock_config.h"
#include "fsl_debug_console.h"
#include "fsl_device_registers.h"
#include "pin_mux.h"
#include "fsl_lpuart.h"
#include "fsl_lpuart_edma.h"
#include "fsl_dmamux.h"
#include "fsl_enet.h"
#include "fsl_phy.h"
#include "fsl_enet_mdio.h"
#include "fsl_phyksz8081.h"
#include "fsl_iomuxc.h"

// JLinkGDBServer -select USB -device MIMXRT1061xxx5A -endian little -if SWD
// -speed 4000 -noir -LocalhostOnly
/*******************************************************************************
 * Definitions
 ******************************************************************************/

/*******************************************************************************
 * Prototypes
 ******************************************************************************/

/*******************************************************************************
 * Code
 ******************************************************************************/
/*!
 * @brief Main function
 */
#define TEST_ENET     ENET2
#define TEST_PHY_ADDR 0x01
uint8_t g_macAddr[6] = {0xd4, 0xbe, 0xd9, 0x45, 0x22, 0x60};
static mdio_handle_t mdioHandle = {.ops = &enet_ops};
static phy_handle_t phyHandle = {.phyAddr = TEST_PHY_ADDR, .mdioHandle = &mdioHandle, .ops = &phyksz8081_ops};

// enet
enet_handle_t g_handle;
/*******************************************************************************
 * Variables
 ******************************************************************************/
/*! @brief Buffer descriptors should be in non-cacheable region and should be align to "ENET_BUFF_ALIGNMENT". */
AT_NONCACHEABLE_SECTION_ALIGN(enet_rx_bd_struct_t g_rxBuffDescrip[4], ENET_BUFF_ALIGNMENT);
AT_NONCACHEABLE_SECTION_ALIGN(enet_tx_bd_struct_t g_txBuffDescrip[4], ENET_BUFF_ALIGNMENT);
/*! @brief The data buffers can be in cacheable region or in non-cacheable region.
 * If use cacheable region, the alignment size should be the maximum size of "CACHE LINE SIZE" and "ENET_BUFF_ALIGNMENT"
 * If use non-cache region, the alignment size is the "ENET_BUFF_ALIGNMENT".
 */
SDK_ALIGN(uint8_t g_rxDataBuff[4][SDK_SIZEALIGN(ENET_FRAME_MAX_FRAMELEN, ENET_BUFF_ALIGNMENT)], ENET_BUFF_ALIGNMENT);
SDK_ALIGN(uint8_t g_txDataBuff[4][SDK_SIZEALIGN(ENET_FRAME_MAX_FRAMELEN, ENET_BUFF_ALIGNMENT)], ENET_BUFF_ALIGNMENT);

uint8_t rx_temp_buf[ENET_FRAME_MAX_FRAMELEN];


void enet_test_main() {
    status_t status;
    uint32_t length;

    while(1){
         status = ENET_GetRxFrameSize(&g_handle, &length, 0);
        /* Call ENET_ReadFrame when there is a received frame. */
        if (length != 0)
        {
            uint8_t *data = rx_temp_buf;
            /* Received valid frame. Deliver the rx buffer with the size equal to length. */
            status        = ENET_ReadFrame(TEST_ENET, &g_handle, data, length, 0, NULL);
            if (status == kStatus_Success)
            {
                PRINTF(" A frame received. the length %d ", length);
                PRINTF(" Dest Address %02x:%02x:%02x:%02x:%02x:%02x Src Address %02x:%02x:%02x:%02x:%02x:%02x \r\n",
                       data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[8], data[9],
                       data[10], data[11]);
            }
        }
        else if (status == kStatus_ENET_RxFrameError)
        {
            enet_data_error_stats_t err;
            /* Update the received buffer when error happened. */
            /* Get the error information of the received g_frame. */
            ENET_GetRxErrBeforeReadFrame(&g_handle, &err, 0);
            /* update the receive buffer. */
            ENET_ReadFrame(TEST_ENET, &g_handle, NULL, 0, 0, NULL);
        }
    }
}

void setup_enet_test() {
  status_t status;
  bool link = false;
  bool autonego = false;
  // setup clock
  {
    /* Set 50MHz output clock required by PHY. */
    const clock_enet_pll_config_t config = {.enableClkOutput = true, .loopDivider = 1};
    CLOCK_InitEnetPll(&config);
    IOMUXC_EnableMode(IOMUXC_GPR, kIOMUXC_GPR_ENET2TxClkOutputDir, true);
    IOMUXC_EnableMode(IOMUXC_GPR, kIOMUXC_GPR_ENET1TxClkOutputDir, true);
  }

  // setup reset
  GPIO_WritePinOutput(BOARD_INITPINS_ENET2_RESET_GPIO, BOARD_INITPINS_ENET2_RESET_GPIO_PIN, 0);
  SDK_DelayAtLeastUs(1000000, CLOCK_GetFreq(kCLOCK_CpuClk));
  GPIO_WritePinOutput(BOARD_INITPINS_ENET2_RESET_GPIO, BOARD_INITPINS_ENET2_RESET_GPIO_PIN, 1);

  phy_config_t phyConfig = {0};
  phyConfig.phyAddr = TEST_PHY_ADDR;
  phyConfig.autoNeg = true;
  mdioHandle.resource.base = ENET2;
  mdioHandle.resource.csrClock_Hz = CLOCK_GetFreq(kCLOCK_IpgClk);
  // check the phy
  do {
    int count = 100;
    status = PHY_Init(&phyHandle, &phyConfig);
    if (status == kStatus_Success) {
      PRINTF("Wait for PHY link up...\r\n");

      /* Wait for auto-negotiation success and link up */
      do {
        PHY_GetAutoNegotiationStatus(&phyHandle, &autonego);
        PHY_GetLinkStatus(&phyHandle, &link);
        if (autonego && link) {
          break;
        }
      } while (--count);
      if (!autonego) {
        PRINTF("PHY Auto-negotiation failed. Please check the cable connection and link partner setting.\r\n");
      }
    }
  } while (!(link && autonego));

  // setup the edma
  PRINTF("Wire connected");
  {
    phy_speed_t speed;
    phy_duplex_t duplex;

    PHY_GetLinkSpeedDuplex(&phyHandle, &speed, &duplex);
    PRINTF("speed %d duplex %d\r\n", speed, duplex);

    // configure the rmii
    enet_config_t config;
    ENET_GetDefaultConfig(&config);
    config.miiSpeed = (enet_mii_speed_t)speed;
    config.miiDuplex = (enet_mii_duplex_t)duplex;

    enet_buffer_config_t buffConfig[] = {{
        4,
        4,
        SDK_SIZEALIGN(ENET_FRAME_MAX_FRAMELEN, ENET_BUFF_ALIGNMENT),
        SDK_SIZEALIGN(ENET_FRAME_MAX_FRAMELEN, ENET_BUFF_ALIGNMENT),
        &g_rxBuffDescrip[0],
        &g_txBuffDescrip[0],
        &g_rxDataBuff[0][0],
        &g_txDataBuff[0][0],
        true,
        true,
        NULL,
    }};

    ENET_Init(TEST_ENET, &g_handle, &config, &buffConfig[0], &g_macAddr[0], CLOCK_GetFreq(kCLOCK_IpgClk));
    ENET_ActiveRead(TEST_ENET);
  }
}

int main(void) {
  /* Init board hardware. */
  BOARD_ConfigMPU();
  BOARD_InitBootPins();
  BOARD_InitBootClocks();
  BOARD_InitDebugConsole();

  setup_enet_test();
  enet_test_main();
  while (1) {
  };
}
